package com.evil.application.util;

import cn.afterturn.easypoi.entity.ImageEntity;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import com.evil.application.enums.form.control.ControlCodeEnum;
import com.evil.application.pojo.dto.form.control.code.BarCodeControlDTO;
import com.evil.application.pojo.dto.form.control.code.ControlDTO;
import com.evil.application.pojo.dto.form.control.code.config.BarCodeControlConfigDTO;
import com.evil.common.application.dto.form.control.ControlBaseDTO;
import com.evil.common.application.dto.order.data.ControlDataDTO;
import com.evil.common.core.enums.RCodeEnum;
import com.evil.common.core.exception.BusinessException;
import com.evil.common.core.util.StreamUtil;
import com.evil.common.file.util.PoiUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.formula.BaseFormulaEvaluator;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import javax.imageio.ImageIO;
import javax.validation.constraints.NotBlank;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 应用打印模板工具类
 *
 * @author liyang
 * @date 2022-08-08 00:00
 */
@Slf4j
public class ApplicationPrintUtil {

    /**
     * 生成打印模板数据map
     *
     * @param controlBases   模板配置列表
     * @param controlDataMap 订单控件数据map
     * @return Map
     */
    public static Map<String, Object> generatePrintMap(List<ControlBaseDTO> controlBases, Map<Long, ControlDataDTO> controlDataMap) {
        // 定义要替换的map
        Map<String, Object> contentMap = new HashMap<>();
        controlBases.forEach(base -> {
            // 不是表格控件
            if (!base.getControlCode().equals(ControlCodeEnum.TABLE.getCode())) {
                // 处理控件模板code
                String fieldCode = base.getControlField();

                ControlDataDTO controlDataDTO = controlDataMap.get(base.getControlId());
                // 通过控件code获取控件枚举
                ControlCodeEnum controlCodeEnum = ControlCodeEnum.findByCode(base.getControlCode());
                // 获取控件值
                String value = controlCodeEnum.getControlDataToString().apply(controlDataDTO);
                contentMap.put(fieldCode, value);
            }
        });
        return contentMap;
    }

    /**
     * 生成打印数据
     *
     * @param data               控件值列表
     * @param controlMap         控件map
     * @param controlCodeEnumMap 控件类型枚举map 性能优化使用
     * @param split              多值打印分割符
     * @return Map
     */
    public static Map<String, Object> generatePrintMap(List<ControlDataDTO> data, Map<Long, ControlDTO> controlMap, Map<String, ControlCodeEnum> controlCodeEnumMap, String split) {
        Function<String, ControlCodeEnum> getControlCodeEnum = code -> {
            if (controlCodeEnumMap.containsKey(code)) {
                return controlCodeEnumMap.get(code);
            } else {
                ControlCodeEnum controlCodeEnum = ControlCodeEnum.findByCode(code);
                controlCodeEnumMap.put(code, controlCodeEnum);
                return controlCodeEnum;
            }
        };
        Function<ControlDataDTO, Object> printFormat = d -> {
            ControlCodeEnum controlCodeEnum = getControlCodeEnum.apply(d.getControlCode());
            ControlDTO controlDTO = controlMap.get(d.getControlId());
            if (controlDTO instanceof BarCodeControlDTO) {
                BarCodeControlDTO barCodeControlDTO = (BarCodeControlDTO) controlDTO;
                BarCodeControlConfigDTO controlConfig = barCodeControlDTO.getControlConfig();
                String code = controlCodeEnum.getControlDataToString().apply(d);
                // 条码内容为空 或者非字母与数字 直接返回
                if (StringUtils.isBlank(code) || !Pattern.matches("[\\w|-]*", code)) {
                    return code;
                }

                byte[] codeData = ApplicationPrintUtil.generatePrintCode(code, controlConfig);
                ImageEntity image = new ImageEntity(codeData, controlConfig.getWidth(), controlConfig.getWidth());
                image.setColspan(controlConfig.getColspan());
                image.setRowspan(controlConfig.getRowspan());
                return image;
            } else {
                return String.join(split, OrderDataUtil.controlDataValues(controlCodeEnum, d.getValues()));
            }
        };
        Map<Long, Object> controlValueMap = StreamUtil.toMapFKV(data, c -> controlMap.containsKey(c.getControlId()), ControlDataDTO::getControlId, printFormat);

        // 过滤存在的字段key
        return StreamUtil.transMapFK(controlValueMap, (k, v) -> controlMap.containsKey(k), c -> controlMap.get(c).getControlField());
    }

    /**
     * 处理打印定制-计算公式
     *
     * @param workbook workbook
     */
    public static void handlePrintCal(Workbook workbook) {
        String calcReg = "###cal###";
        Pattern fontSizeByLengthReg = Pattern.compile("###fontSizeByLength\\((.*)\\)###");
        BaseFormulaEvaluator formulaEvaluator = PoiUtil.getFormulaEvaluator(workbook);
        for (Sheet sheet : workbook) {
            for (Row row : sheet) {
                for (Cell cell : row) {
                    Object valueObj = PoiUtil.getCellValue(cell);
                    if (!Objects.nonNull(valueObj)) {
                        continue;
                    }
                    String value = String.valueOf(valueObj);

                    // 定制-计算公式
                    if (value.startsWith(calcReg)) {
                        cell.setCellFormula(value.replaceAll(calcReg, ""));
                        formulaEvaluator.evaluateFormulaCell(cell);
                        continue;
                    }

                    // 字体大小根据内容长度设置
                    // 内容长度，字体大小|内容长度，字体大小|内容长度，字体大小
                    // ###fontSizeByLength(10,15|16,12|18,10)###
                    Matcher matcher = fontSizeByLengthReg.matcher(value);
                    // 只需要取1个即可
                    if (matcher.find()) {
                        String content = StringUtils.replace(value, matcher.group(0), "");
                        cell.setCellValue(content);
                        int contentLength = content.length();
                        String funcArgs = matcher.group(1);
                        String[] funcConfigSplits = funcArgs.split("\\|");
                        short fontSize = -1;
                        for (String funcConfigSplit : funcConfigSplits) {
                            String[] split = funcConfigSplit.split(",");
                            int length = Integer.parseInt(split[0]);
                            short size = Short.parseShort(split[1]);
                            // 取最后一个符合的
                            if (contentLength >= length) {
                                fontSize = size;
                            }
                        }
                        if (fontSize >= 0) {
                            org.apache.poi.ss.usermodel.Font font = workbook.getFontAt(cell.getCellStyle().getFontIndexAsInt());
                            font.setFontHeightInPoints(fontSize);
                            cell.getCellStyle().setFont(font);
                        }
                    }
                }
            }
        }
    }

    /**
     * 生成条形码
     *
     * @param code          内容
     * @param controlConfig controlConfig
     * @return byte[]
     */
    private static byte[] generatePrintCode(@NotBlank(message = "code cant not blank") String code, BarCodeControlConfigDTO controlConfig) {
        String format = "png";
        Hashtable<EncodeHintType, String> hints = new Hashtable<>();
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");

        String tempFile = FileUtil.getTmpDirPath() + System.currentTimeMillis() + "." + format;
        try {
            // 1.创建图片
            FileOutputStream bos = new FileOutputStream(tempFile);
            BitMatrix bitMatrix = new MultiFormatWriter().encode(code, BarcodeFormat.CODE_93, controlConfig.getWidth(), controlConfig.getHeight(), hints);
            MatrixToImageWriter.writeToStream(bitMatrix, format, bos);
            bos.close();

            if (controlConfig.getShowText()) {
                // 2.写文字
                create(code, tempFile, format, controlConfig.getWidth(), controlConfig.getHeight(), controlConfig.getFontSize());
            }

            return FileUtil.readBytes(tempFile);
        } catch (Exception e) {
            log.error("【生成打印条形码失败】", e);
            throw new BusinessException(RCodeEnum.ERROR_RUNTIME);
        } finally {
            try {
                FileUtil.del(tempFile);
            } catch (IORuntimeException e) {
                log.error("【生成打印条形码失败】", e);
            }
        }
    }

    private static void create(String code, String tempFile, String format, int width, int height, int fontSize) throws IOException {
        width += fontSize;
        height += fontSize;
        Image image = ImageIO.read(new File(tempFile));
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = bi.createGraphics();
        g2.setBackground(Color.WHITE);
        g2.clearRect(0, 0, width, height);
        g2.drawImage(image, 0, 0, width - fontSize, height - fontSize, null);
        // 设置生成图片的文字样式
        Font font = new Font("宋体", Font.PLAIN, fontSize);
        g2.setFont(font);
        g2.setPaint(Color.BLACK);
        // 设置字体在图片中的位置 在这里是居中
        FontRenderContext context = g2.getFontRenderContext();
        Rectangle2D bounds = font.getStringBounds(code, context);
        double x = (width - bounds.getWidth()) / 2;
        double y = (height - bounds.getHeight());
        double ascent = -bounds.getY();
        double baseY = y + ascent;
        // 防止生成的文字带有锯齿
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        // 在图片上生成文字
        g2.drawString(code, (int) x, (int) baseY);
        // 重写到文件
        ImageIO.write(bi, format, new File(tempFile));
    }
}
